package cn.cj.erp.common.security.security.service;

import cn.cj.erp.common.constants.HttpHeadConstants;
import cn.cj.erp.common.constants.SystemConstants;
import cn.cj.erp.common.exception.ServiceException;
import cn.cj.erp.common.redis.RedisKeyEnum;
import cn.cj.erp.common.security.security.user.LoginUser;
import cn.cj.erp.common.utils.ServletUtils;
import cn.cj.erp.common.utils.id.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import cn.cj.erp.common.redis.RedisCache;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Token工具类
 *
 * @author chenjian
 */
@Slf4j
@Service
public class TokenService {
    @Resource
    private RedisCache redisCache;
    // 自定义 Token 的http头
    @Value("${token.header}")
    private String header;
    // 令牌秘钥
    @Value("${token.secret}")
    private String secret;

    // 令牌有效期（默认120分钟）
    @Value("${token.expireTime}")
    private long expireTime;

    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

    /**
     * 获取登录用户信息
     * @return 用户信息
     */
    public LoginUser getLoginUser(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StrUtil.isEmpty(token)) {
            return null;
        }
        Long userId;
        try {
            Claims claims = parseToken(token);
            // 解析对应的权限以及用户信息
            userId = MapUtils.getLong(claims, SystemConstants.JWT_LOGIN_USERID);
        } catch (Exception e) {
            log.error("根据Token【{}】，从JWT解密信息异常\n", token, e);
            throw new ServiceException(String.format("根据Token【%s】，从JWT解密信息异常", token));
        }
        LoginUser loginUser = redisCache.get(RedisKeyEnum.LOGIN_TOKEN.getKey(userId), LoginUser.class);
        return loginUser;
    }

    /**
     * 设置用户身份信息
     */
    public void setLoginUser(LoginUser loginUser) {
        if (ObjectUtil.isNotNull(loginUser) && ObjectUtil.isNotNull(loginUser.getUserId())) {
            refreshToken(loginUser);
        }
    }

    /**
     * 删除用户身份信息
     */
    public void delLoginUser(LoginUser loginUser) {
        if (ObjectUtil.isNotNull(loginUser) && ObjectUtil.isNotNull(loginUser.getUserId())) {
            redisCache.delete(RedisKeyEnum.LOGIN_TOKEN.getKey(loginUser.getUserId()));
        }
    }

    /**
     * 创建令牌
     *
     * @param loginUser 用户信息
     * @return 令牌
     */
    public String createToken(LoginUser loginUser) {
        String uuid = IdUtil.fastSimpleUUID();
        loginUser.setToken(uuid);
        fillUserAgentInfo(loginUser);
        refreshToken(loginUser);
        Map<String, Object> claims = new HashMap<>(1);
        claims.put(SystemConstants.JWT_LOGIN_UUID, uuid);
        claims.put(SystemConstants.JWT_LOGIN_USERID, loginUser.getUserId());
        return createToken(claims);
    }

    /**
     * 刷新令牌有效期，redis缓存的key为userId
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser) {
        loginUser.setLoginTime(new Date());
        loginUser.setExpireTime(new Date(System.currentTimeMillis() + expireTime  * 60 * 1000));
        // 根据登录用户名将loginUser缓存
        redisCache.set(RedisKeyEnum.LOGIN_TOKEN.getKey(loginUser.getUserId()), loginUser, expireTime * 60);
    }

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     *
     * @param loginUser 登录用户
     */
    public void verifyToken(LoginUser loginUser) {
        if (loginUser.getExpireTime().getTime() - System.currentTimeMillis() <= MILLIS_MINUTE_TEN) {
            refreshToken(loginUser);
        }
    }

    /**
     * 从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String createToken(Map<String, Object> claims) {
        return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    /**
     * 从令牌中获取数据声明
     *
     * @param token 令牌
     * @return 数据声明
     */
    private Claims parseToken(String token) {
        return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
    }

    /**
     * 获取请求token
     * @return token
     */
    private String getToken(HttpServletRequest request) {
        return request.getHeader(header);
    }

    /**
     * 设置用户代理信息
     *
     * @param loginUser 登录信息
     */
    private void fillUserAgentInfo(LoginUser loginUser) {
        UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader(HttpHeadConstants.USER_AGENT));
        String ip = ServletUtil.getClientIP(ServletUtils.getRequest());
        loginUser.setIp(ip);
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOs().getName());
    }
}
